using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Phoenix.Diagnostics;
using Phoenix.Exceptions;
using Phoenix.Frames;
using Phoenix.Framework;
using Phoenix.Storage;
using Phoenix.Utilities;

namespace Phoenix
{
    public class PhoenixHostBuilder : IPhoenixHostBuilder
    {
        protected readonly List<Assembly> AssembliesToRegister = new List<Assembly>();
        readonly Dispatcher appDispatcher;
        readonly TaskScheduler appTaskScheduler;

        public PhoenixHostBuilder(TaskScheduler appTaskScheduler, Dispatcher appDispatcher)
        {
            this.appTaskScheduler = appTaskScheduler;
            this.appDispatcher = appDispatcher;
            Configuration = new PhoenixConfiguration();
        }

        protected Func<INavigationFrame> NavigationFrameFactory { get; private set; }
        protected IErrorReporter ErrorReporter { get; private set; }
        protected IControllerLocator ControllerLocator { get; private set; }
        protected IControllerFactory ControllerFactory { get; private set; }
        protected IViewLocator ViewLocator { get; private set; }

        public IPhoenixHostBuilder UseControllerLocator<TLocator>(TLocator locator) where TLocator : IControllerLocator
        {
            ControllerLocator = locator;
            return this;
        }

        public IPhoenixHostBuilder UseErrorReporter<TErrorReporter>(TErrorReporter errorReporter) where TErrorReporter : IErrorReporter
        {
            ErrorReporter = errorReporter;
            return this;
        }

        public IPhoenixHostBuilder UseViewLocator<TViewLocator>(TViewLocator viewLocator) where TViewLocator : IViewLocator
        {
            ViewLocator = viewLocator;
            return this;
        }

        public IPhoenixHostBuilder UseControllerFactory<TControllerFactory>(TControllerFactory controllerFactory) where TControllerFactory : IControllerFactory
        {
            ControllerFactory = controllerFactory;
            return this;
        }

        public IPhoenixHostBuilder SetNavigationFrameFactory(Func<INavigationFrame> navigationFrameFactory)
        {
            NavigationFrameFactory = navigationFrameFactory;
            return this;
        }

        public PhoenixConfiguration Configuration { get; private set; }

        public IPhoenixHostBuilder RegisterAssembly(Assembly assembly)
        {
            AssembliesToRegister.Add(assembly);
            return this;
        }

        public virtual Task<IPhoenixHost> Run(IPhoenixHostTarget hostTarget)
        {
            var hostState = new TransientStore();

            IControllerLocator controllerLocator = ControllerLocator ?? new DefaultControllerLocator();
            IControllerFactory controllerFactory = ControllerFactory ?? new DefaultControllerFactory();
            IViewLocator viewLocator = ViewLocator ?? new DefaultViewLocator();
            IErrorReporter errorReporter = GetErrorReporter();
            IActionInvoker actionInvoker = null;
            // Action Invoker is a circular dependency, so need modified closure in the Lazy initialiser 
            // ReSharper disable AccessToModifiedClosure
            var hostFactory = new Func<IPhoenixHost>(() =>
                new PhoenixHost(hostTarget, viewLocator, controllerLocator, hostState, errorReporter, actionInvoker,
#if WINDOWS_PHONE
                new Tasks.PhoneTaskManager(), 
#endif
                appDispatcher,
                appTaskScheduler));
            // ReSharper restore AccessToModifiedClosure
            actionInvoker = new ActionInvoker(errorReporter, hostFactory, controllerFactory);
            IPhoenixHost host = hostFactory();

            return RunBase(hostTarget, actionInvoker, host, hostState)
                .ContinueWith(t => host);
        }

        protected Task RunBase(IPhoenixHostTarget hostTarget, IActionInvoker actionInvoker, IPhoenixHost host, TransientStore hostState)
        {
            Assembly targetAssembly = hostTarget.GetType().Assembly;
            var hostFrame = (NavigationFrameFactory ?? (() => new NavigationFrame()))();
            hostFrame.InitialiseHost(host);
            hostFrame.Journal.Initialise(hostState);
            host.RegisterAssembly(targetAssembly);
            foreach (Assembly assembly in AssembliesToRegister)
            {
                host.RegisterAssembly(assembly);
            }

#if !WINDOWS_PHONE
            // Only valid option for WPF
            if ((Configuration.StartupOptions & PhoenixStartupOptions.ShowShellAfterInitialNavigation) != PhoenixStartupOptions.ShowShellAfterInitialNavigation)
            {
#endif
                hostTarget.Show(hostFrame);                
#if !WINDOWS_PHONE
            }
#endif

                return InvokeDefaultControllerAction(targetAssembly, actionInvoker, hostFrame, host);
        }

        protected IErrorReporter GetErrorReporter()
        {
#if WINDOWS_PHONE
            return ErrorReporter ?? new LittleWatson();
#else
            return ErrorReporter ?? new TraceErrorReporter();
#endif
        }

        protected Task InvokeDefaultControllerAction(Assembly appAssembly, IActionInvoker actionInvoker, INavigationFrame hostFrame, IPhoenixHost host)
        {
            return Task.Factory.StartNew(() =>
                {
                    InvokeActionRequest invokeActionRequest;
                    ControllerAction controllerAction;
                    if (hostFrame.Journal.Count > 0)
                    {
                        NavigationRequest restore = hostFrame.Journal.Pop();
                        controllerAction = restore.ControllerAction;
                        var controllerActionParameters = restore.Parameters ?? Enumerable.Empty<ControllerActionParameter>();
                        invokeActionRequest = new InvokeActionRequest(null, controllerAction, controllerActionParameters, restore.Options, hostFrame, restore.RequestTransientStore);
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(Configuration.DefaultControllerAction))
                            throw new ControllerActionInvocationFailedException("No Default Controller Action Specified");

                        controllerAction = ControllerAction.CreateFrom(Configuration.DefaultControllerAction, appAssembly.GetAssemblyName());
                        var noProgress = (Configuration.StartupOptions & PhoenixStartupOptions.NoProgress) == PhoenixStartupOptions.NoProgress;

                        var options = noProgress ? InvokeOptions.NoProgress : InvokeOptions.None;
                        var parameters = Configuration.DefaultControllerActionArguments == null
                                             ? null
                                             : Configuration.DefaultControllerActionArguments.Select(ControllerActionParameter.FromObject);
                        invokeActionRequest = new InvokeActionRequest(null, controllerAction, parameters, options, hostFrame);
                    }

                    if (controllerAction == null)
                    {
                        var message = string.Format("{0} is not a valid default controller action", Configuration.DefaultControllerAction);
                        throw new InvalidOperationException(message);
                    }

                    return new
                        {
                            invokeActionRequest, controllerAction
                        };
                }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
                .ContinueWith(t =>
                    {
                        actionInvoker.InvokeAction(t.Result.invokeActionRequest).Wait();
                        return t.Result;
                    })
                .ContinueWith(t =>
                    {
#if !WINDOWS_PHONE
                        // Only valid option for WPF
                        const PhoenixStartupOptions shellAfterInitialNavigation = PhoenixStartupOptions.ShowShellAfterInitialNavigation;

                        if ((Configuration.StartupOptions & shellAfterInitialNavigation) == shellAfterInitialNavigation)
                        {
                            host.HostTarget.Show(hostFrame);
                        }
#endif

                        if (t.Exception == null)
                        {
                            return;
                        }
                        if (t.Exception.InnerException is ControllerNotFoundException)
                        {
                            string message = CreateExceptionMessage(t.Result.controllerAction);
                            throw new ControllerActionInvocationFailedException(message, t.Exception.InnerException);
                        }
                        if (t.Exception.InnerException is ActionNotFoundException)
                        {
                            string message = CreateExceptionMessage(t.Result.controllerAction);
                            throw new ControllerActionInvocationFailedException(message, t.Exception.InnerException);
                        }
                    }
#if !WINDOWS_PHONE
                    , appTaskScheduler
#endif
);
        }

        static string CreateExceptionMessage(ControllerAction controllerAction)
        {
            return string.Format(@"Failed to invoke the default controller action {0}. 
Either create that controller action, or configure the default controller action to another action.

Example controller:
public class {1}Controller : Phoenix.Controller
{{
    public ActionResult {2}()
    {{
        return Page(new {2}ViewModel());
    }}
}}", controllerAction, controllerAction.Controller, controllerAction.Action);
        }
    }
}
